(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
        (global.i18next = factory());
}(this, (function () {
    'use strict';

    var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
        return typeof obj;
    } : function (obj) {
        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
    };











    var classCallCheck = function (instance, Constructor) {
        if (!(instance instanceof Constructor)) {
            throw new TypeError("Cannot call a class as a function");
        }
    };









    var _extends = Object.assign || function (target) {
        for (var i = 1; i < arguments.length; i++) {
            var source = arguments[i];

            for (var key in source) {
                if (Object.prototype.hasOwnProperty.call(source, key)) {
                    target[key] = source[key];
                }
            }
        }

        return target;
    };



    var inherits = function (subClass, superClass) {
        if (typeof superClass !== "function" && superClass !== null) {
            throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
        }

        subClass.prototype = Object.create(superClass && superClass.prototype, {
            constructor: {
                value: subClass,
                enumerable: false,
                writable: true,
                configurable: true
            }
        });
        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    };











    var possibleConstructorReturn = function (self, call) {
        if (!self) {
            throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
        }

        return call && (typeof call === "object" || typeof call === "function") ? call : self;
    };





    var slicedToArray = function () {
        function sliceIterator(arr, i) {
            var _arr = [];
            var _n = true;
            var _d = false;
            var _e = undefined;

            try {
                for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
                    _arr.push(_s.value);

                    if (i && _arr.length === i) break;
                }
            } catch (err) {
                _d = true;
                _e = err;
            } finally {
                try {
                    if (!_n && _i["return"]) _i["return"]();
                } finally {
                    if (_d) throw _e;
                }
            }

            return _arr;
        }

        return function (arr, i) {
            if (Array.isArray(arr)) {
                return arr;
            } else if (Symbol.iterator in Object(arr)) {
                return sliceIterator(arr, i);
            } else {
                throw new TypeError("Invalid attempt to destructure non-iterable instance");
            }
        };
    }();













    var toConsumableArray = function (arr) {
        if (Array.isArray(arr)) {
            for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];

            return arr2;
        } else {
            return Array.from(arr);
        }
    };

    var consoleLogger = {
        type: 'logger',

        log: function log(args) {
            this.output('log', args);
        },
        warn: function warn(args) {
            this.output('warn', args);
        },
        error: function error(args) {
            this.output('error', args);
        },
        output: function output(type, args) {
            var _console;

            /* eslint no-console: 0 */
            if (console && console[type])(_console = console)[type].apply(_console, toConsumableArray(args));
        }
    };

    var Logger = function () {
        function Logger(concreteLogger) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
            classCallCheck(this, Logger);

            this.init(concreteLogger, options);
        }

        Logger.prototype.init = function init(concreteLogger) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

            this.prefix = options.prefix || 'i18next:';
            this.logger = concreteLogger || consoleLogger;
            this.options = options;
            this.debug = options.debug;
        };

        Logger.prototype.setDebug = function setDebug(bool) {
            this.debug = bool;
        };

        Logger.prototype.log = function log() {
            for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
                args[_key] = arguments[_key];
            }

            return this.forward(args, 'log', '', true);
        };

        Logger.prototype.warn = function warn() {
            for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
                args[_key2] = arguments[_key2];
            }

            return this.forward(args, 'warn', '', true);
        };

        Logger.prototype.error = function error() {
            for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
                args[_key3] = arguments[_key3];
            }

            return this.forward(args, 'error', '');
        };

        Logger.prototype.deprecate = function deprecate() {
            for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
                args[_key4] = arguments[_key4];
            }

            return this.forward(args, 'warn', 'WARNING DEPRECATED: ', true);
        };

        Logger.prototype.forward = function forward(args, lvl, prefix, debugOnly) {
            if (debugOnly && !this.debug) return null;
            if (typeof args[0] === 'string') args[0] = '' + prefix + this.prefix + ' ' + args[0];
            return this.logger[lvl](args);
        };

        Logger.prototype.create = function create(moduleName) {
            return new Logger(this.logger, _extends({
                prefix: this.prefix + ':' + moduleName + ':'
            }, this.options));
        };

        return Logger;
    }();

    var baseLogger = new Logger();

    var EventEmitter = function () {
        function EventEmitter() {
            classCallCheck(this, EventEmitter);

            this.observers = {};
        }

        EventEmitter.prototype.on = function on(events, listener) {
            var _this = this;

            events.split(' ').forEach(function (event) {
                _this.observers[event] = _this.observers[event] || [];
                _this.observers[event].push(listener);
            });
        };

        EventEmitter.prototype.off = function off(event, listener) {
            var _this2 = this;

            if (!this.observers[event]) {
                return;
            }

            this.observers[event].forEach(function () {
                if (!listener) {
                    delete _this2.observers[event];
                } else {
                    var index = _this2.observers[event].indexOf(listener);
                    if (index > -1) {
                        _this2.observers[event].splice(index, 1);
                    }
                }
            });
        };

        EventEmitter.prototype.emit = function emit(event) {
            for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
                args[_key - 1] = arguments[_key];
            }

            if (this.observers[event]) {
                var cloned = [].concat(this.observers[event]);
                cloned.forEach(function (observer) {
                    observer.apply(undefined, args);
                });
            }

            if (this.observers['*']) {
                var _cloned = [].concat(this.observers['*']);
                _cloned.forEach(function (observer) {
                    observer.apply(observer, [event].concat(args));
                });
            }
        };

        return EventEmitter;
    }();

    function makeString(object) {
        if (object == null) return '';
        /* eslint prefer-template: 0 */
        return '' + object;
    }

    function copy(a, s, t) {
        a.forEach(function (m) {
            if (s[m]) t[m] = s[m];
        });
    }

    function getLastOfPath(object, path, Empty) {
        function cleanKey(key) {
            return key && key.indexOf('###') > -1 ? key.replace(/###/g, '.') : key;
        }

        function canNotTraverseDeeper() {
            return !object || typeof object === 'string';
        }

        var stack = typeof path !== 'string' ? [].concat(path) : path.split('.');
        while (stack.length > 1) {
            if (canNotTraverseDeeper()) return {};

            var key = cleanKey(stack.shift());
            if (!object[key] && Empty) object[key] = new Empty();
            object = object[key];
        }

        if (canNotTraverseDeeper()) return {};
        return {
            obj: object,
            k: cleanKey(stack.shift())
        };
    }

    function setPath(object, path, newValue) {
        var _getLastOfPath = getLastOfPath(object, path, Object),
            obj = _getLastOfPath.obj,
            k = _getLastOfPath.k;

        obj[k] = newValue;
    }

    function pushPath(object, path, newValue, concat) {
        var _getLastOfPath2 = getLastOfPath(object, path, Object),
            obj = _getLastOfPath2.obj,
            k = _getLastOfPath2.k;

        obj[k] = obj[k] || [];
        if (concat) obj[k] = obj[k].concat(newValue);
        if (!concat) obj[k].push(newValue);
    }

    function getPath(object, path) {
        var _getLastOfPath3 = getLastOfPath(object, path),
            obj = _getLastOfPath3.obj,
            k = _getLastOfPath3.k;

        if (!obj) return undefined;
        return obj[k];
    }

    function deepExtend(target, source, overwrite) {
        /* eslint no-restricted-syntax: 0 */
        for (var prop in source) {
            if (prop in target) {
                // If we reached a leaf string in target or source then replace with source or skip depending on the 'overwrite' switch
                if (typeof target[prop] === 'string' || target[prop] instanceof String || typeof source[prop] === 'string' || source[prop] instanceof String) {
                    if (overwrite) target[prop] = source[prop];
                } else {
                    deepExtend(target[prop], source[prop], overwrite);
                }
            } else {
                target[prop] = source[prop];
            }
        }
        return target;
    }

    function regexEscape(str) {
        /* eslint no-useless-escape: 0 */
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
    }

    /* eslint-disable */
    var _entityMap = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': '&quot;',
        "'": '&#39;',
        "/": '&#x2F;'
    };
    /* eslint-enable */

    function escape(data) {
        if (typeof data === 'string') {
            return data.replace(/[&<>"'\/]/g, function (s) {
                return _entityMap[s];
            });
        }

        return data;
    }

    var ResourceStore = function (_EventEmitter) {
        inherits(ResourceStore, _EventEmitter);

        function ResourceStore(data) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
                ns: ['translation'],
                defaultNS: 'translation'
            };
            classCallCheck(this, ResourceStore);

            var _this = possibleConstructorReturn(this, _EventEmitter.call(this));

            _this.data = data || {};
            _this.options = options;
            if (_this.options.keySeparator === undefined) {
                _this.options.keySeparator = '.';
            }
            return _this;
        }

        ResourceStore.prototype.addNamespaces = function addNamespaces(ns) {
            if (this.options.ns.indexOf(ns) < 0) {
                this.options.ns.push(ns);
            }
        };

        ResourceStore.prototype.removeNamespaces = function removeNamespaces(ns) {
            var index = this.options.ns.indexOf(ns);
            if (index > -1) {
                this.options.ns.splice(index, 1);
            }
        };

        ResourceStore.prototype.getResource = function getResource(lng, ns, key) {
            var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};

            var keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;

            var path = [lng, ns];
            if (key && typeof key !== 'string') path = path.concat(key);
            if (key && typeof key === 'string') path = path.concat(keySeparator ? key.split(keySeparator) : key);

            if (lng.indexOf('.') > -1) {
                path = lng.split('.');
            }

            return getPath(this.data, path);
        };

        ResourceStore.prototype.addResource = function addResource(lng, ns, key, value) {
            var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
                silent: false
            };

            var keySeparator = this.options.keySeparator;
            if (keySeparator === undefined) keySeparator = '.';

            var path = [lng, ns];
            if (key) path = path.concat(keySeparator ? key.split(keySeparator) : key);

            if (lng.indexOf('.') > -1) {
                path = lng.split('.');
                value = ns;
                ns = path[1];
            }

            this.addNamespaces(ns);

            setPath(this.data, path, value);

            if (!options.silent) this.emit('added', lng, ns, key, value);
        };

        ResourceStore.prototype.addResources = function addResources(lng, ns, resources) {
            var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
                silent: false
            };

            /* eslint no-restricted-syntax: 0 */
            for (var m in resources) {
                if (typeof resources[m] === 'string') this.addResource(lng, ns, m, resources[m], {
                    silent: true
                });
            }
            if (!options.silent) this.emit('added', lng, ns, resources);
        };

        ResourceStore.prototype.addResourceBundle = function addResourceBundle(lng, ns, resources, deep, overwrite) {
            var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {
                silent: false
            };

            var path = [lng, ns];
            if (lng.indexOf('.') > -1) {
                path = lng.split('.');
                deep = resources;
                resources = ns;
                ns = path[1];
            }

            this.addNamespaces(ns);

            var pack = getPath(this.data, path) || {};

            if (deep) {
                deepExtend(pack, resources, overwrite);
            } else {
                pack = _extends({}, pack, resources);
            }

            setPath(this.data, path, pack);

            if (!options.silent) this.emit('added', lng, ns, resources);
        };

        ResourceStore.prototype.removeResourceBundle = function removeResourceBundle(lng, ns) {
            if (this.hasResourceBundle(lng, ns)) {
                delete this.data[lng][ns];
            }
            this.removeNamespaces(ns);

            this.emit('removed', lng, ns);
        };

        ResourceStore.prototype.hasResourceBundle = function hasResourceBundle(lng, ns) {
            return this.getResource(lng, ns) !== undefined;
        };

        ResourceStore.prototype.getResourceBundle = function getResourceBundle(lng, ns) {
            if (!ns) ns = this.options.defaultNS;

            // COMPATIBILITY: remove extend in v2.1.0
            if (this.options.compatibilityAPI === 'v1') return _extends({}, this.getResource(lng, ns));

            return this.getResource(lng, ns);
        };

        ResourceStore.prototype.toJSON = function toJSON() {
            return this.data;
        };

        return ResourceStore;
    }(EventEmitter);

    var postProcessor = {

        processors: {},

        addPostProcessor: function addPostProcessor(module) {
            this.processors[module.name] = module;
        },
        handle: function handle(processors, value, key, options, translator) {
            var _this = this;

            processors.forEach(function (processor) {
                if (_this.processors[processor]) value = _this.processors[processor].process(value, key, options, translator);
            });

            return value;
        }
    };

    var Translator = function (_EventEmitter) {
        inherits(Translator, _EventEmitter);

        function Translator(services) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
            classCallCheck(this, Translator);

            var _this = possibleConstructorReturn(this, _EventEmitter.call(this));

            copy(['resourceStore', 'languageUtils', 'pluralResolver', 'interpolator', 'backendConnector', 'i18nFormat'], services, _this);

            _this.options = options;
            if (_this.options.keySeparator === undefined) {
                _this.options.keySeparator = '.';
            }

            _this.logger = baseLogger.create('translator');
            return _this;
        }

        Translator.prototype.changeLanguage = function changeLanguage(lng) {
            if (lng) this.language = lng;
        };

        Translator.prototype.exists = function exists(key) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
                interpolation: {}
            };

            var resolved = this.resolve(key, options);
            return resolved && resolved.res !== undefined;
        };

        Translator.prototype.extractFromKey = function extractFromKey(key, options) {
            var nsSeparator = options.nsSeparator || this.options.nsSeparator;
            if (nsSeparator === undefined) nsSeparator = ':';

            var keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;

            var namespaces = options.ns || this.options.defaultNS;
            if (nsSeparator && key.indexOf(nsSeparator) > -1) {
                var parts = key.split(nsSeparator);
                if (nsSeparator !== keySeparator || nsSeparator === keySeparator && this.options.ns.indexOf(parts[0]) > -1) namespaces = parts.shift();
                key = parts.join(keySeparator);
            }
            if (typeof namespaces === 'string') namespaces = [namespaces];

            return {
                key: key,
                namespaces: namespaces
            };
        };

        Translator.prototype.translate = function translate(keys, options) {
            var _this2 = this;

            if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object' && this.options.overloadTranslationOptionHandler) {
                /* eslint prefer-rest-params: 0 */
                options = this.options.overloadTranslationOptionHandler(arguments);
            }
            if (!options) options = {};

            // non valid keys handling
            if (keys === undefined || keys === null || keys === '') return '';
            if (typeof keys === 'number') keys = String(keys);
            if (typeof keys === 'string') keys = [keys];

            // separators
            var keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;

            // get namespace(s)

            var _extractFromKey = this.extractFromKey(keys[keys.length - 1], options),
                key = _extractFromKey.key,
                namespaces = _extractFromKey.namespaces;

            var namespace = namespaces[namespaces.length - 1];

            // return key on CIMode
            var lng = options.lng || this.language;
            var appendNamespaceToCIMode = options.appendNamespaceToCIMode || this.options.appendNamespaceToCIMode;
            if (lng && lng.toLowerCase() === 'cimode') {
                if (appendNamespaceToCIMode) {
                    var nsSeparator = options.nsSeparator || this.options.nsSeparator;
                    return namespace + nsSeparator + key;
                }

                return key;
            }

            // resolve from store
            var resolved = this.resolve(keys, options);
            var res = resolved && resolved.res;
            var resUsedKey = resolved && resolved.usedKey || key;

            var resType = Object.prototype.toString.apply(res);
            var noObject = ['[object Number]', '[object Function]', '[object RegExp]'];
            var joinArrays = options.joinArrays !== undefined ? options.joinArrays : this.options.joinArrays;

            // object
            var handleAsObject = typeof res !== 'string' && typeof res !== 'boolean' && typeof res !== 'number';
            if (res && handleAsObject && noObject.indexOf(resType) < 0 && !(joinArrays && resType === '[object Array]')) {
                if (!options.returnObjects && !this.options.returnObjects) {
                    this.logger.warn('accessing an object - but returnObjects options is not enabled!');
                    return this.options.returnedObjectHandler ? this.options.returnedObjectHandler(resUsedKey, res, options) : 'key \'' + key + ' (' + this.language + ')\' returned an object instead of string.';
                }

                // if we got a separator we loop over children - else we just return object as is
                // as having it set to false means no hierarchy so no lookup for nested values
                if (keySeparator) {
                    var copy$$1 = resType === '[object Array]' ? [] : {}; // apply child translation on a copy

                    /* eslint no-restricted-syntax: 0 */
                    for (var m in res) {
                        if (Object.prototype.hasOwnProperty.call(res, m)) {
                            var deepKey = '' + resUsedKey + keySeparator + m;
                            copy$$1[m] = this.translate(deepKey, _extends({}, options, {
                                joinArrays: false,
                                ns: namespaces
                            }));
                            if (copy$$1[m] === deepKey) copy$$1[m] = res[m]; // if nothing found use orginal value as fallback
                        }
                    }
                    res = copy$$1;
                }
            } else if (joinArrays && resType === '[object Array]') {
                // array special treatment
                res = res.join(joinArrays);
                if (res) res = this.extendTranslation(res, keys, options);
            } else {
                // string, empty or null
                var usedDefault = false;
                var usedKey = false;

                // fallback value
                if (!this.isValidLookup(res) && options.defaultValue !== undefined) {
                    usedDefault = true;
                    res = options.defaultValue;
                }
                if (!this.isValidLookup(res)) {
                    usedKey = true;
                    res = key;
                }

                // save missing
                var updateMissing = options.defaultValue && options.defaultValue !== res && this.options.updateMissing;
                if (usedKey || usedDefault || updateMissing) {
                    this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? options.defaultValue : res);

                    var lngs = [];
                    var fallbackLngs = this.languageUtils.getFallbackCodes(this.options.fallbackLng, options.lng || this.language);
                    if (this.options.saveMissingTo === 'fallback' && fallbackLngs && fallbackLngs[0]) {
                        for (var i = 0; i < fallbackLngs.length; i++) {
                            lngs.push(fallbackLngs[i]);
                        }
                    } else if (this.options.saveMissingTo === 'all') {
                        lngs = this.languageUtils.toResolveHierarchy(options.lng || this.language);
                    } else {
                        lngs.push(options.lng || this.language);
                    }

                    var send = function send(l, k) {
                        if (_this2.options.missingKeyHandler) {
                            _this2.options.missingKeyHandler(l, namespace, k, updateMissing ? options.defaultValue : res, updateMissing, options);
                        } else if (_this2.backendConnector && _this2.backendConnector.saveMissing) {
                            _this2.backendConnector.saveMissing(l, namespace, k, updateMissing ? options.defaultValue : res, updateMissing, options);
                        }
                        _this2.emit('missingKey', l, namespace, k, res);
                    };

                    if (this.options.saveMissing) {
                        if (this.options.saveMissingPlurals && options.count) {
                            lngs.forEach(function (l) {
                                var plurals = _this2.pluralResolver.getPluralFormsOfKey(l, key);

                                plurals.forEach(function (p) {
                                    return send([l], p);
                                });
                            });
                        } else {
                            send(lngs, key);
                        }
                    }
                }

                // extend
                res = this.extendTranslation(res, keys, options, resolved);

                // append namespace if still key
                if (usedKey && res === key && this.options.appendNamespaceToMissingKey) res = namespace + ':' + key;

                // parseMissingKeyHandler
                if (usedKey && this.options.parseMissingKeyHandler) res = this.options.parseMissingKeyHandler(res);
            }

            // return
            return res;
        };

        Translator.prototype.extendTranslation = function extendTranslation(res, key, options, resolved) {
            var _this3 = this;

            if (this.i18nFormat && this.i18nFormat.parse) {
                res = this.i18nFormat.parse(res, options, resolved.usedLng, resolved.usedNS, resolved.usedKey);
            } else if (!options.skipInterpolation) {
                // i18next.parsing
                if (options.interpolation) this.interpolator.init(_extends({}, options, {
                    interpolation: _extends({}, this.options.interpolation, options.interpolation)
                }));

                // interpolate
                var data = options.replace && typeof options.replace !== 'string' ? options.replace : options;
                if (this.options.interpolation.defaultVariables) data = _extends({}, this.options.interpolation.defaultVariables, data);
                res = this.interpolator.interpolate(res, data, options.lng || this.language);

                // nesting
                if (options.nest !== false) res = this.interpolator.nest(res, function () {
                    return _this3.translate.apply(_this3, arguments);
                }, options);

                if (options.interpolation) this.interpolator.reset();
            }

            // post process
            var postProcess = options.postProcess || this.options.postProcess;
            var postProcessorNames = typeof postProcess === 'string' ? [postProcess] : postProcess;

            if (res !== undefined && res !== null && postProcessorNames && postProcessorNames.length && options.applyPostProcessor !== false) {
                res = postProcessor.handle(postProcessorNames, res, key, options, this);
            }

            return res;
        };

        Translator.prototype.resolve = function resolve(keys) {
            var _this4 = this;

            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

            var found = void 0;
            var usedKey = void 0;
            var usedLng = void 0;
            var usedNS = void 0;

            if (typeof keys === 'string') keys = [keys];

            // forEach possible key
            keys.forEach(function (k) {
                if (_this4.isValidLookup(found)) return;
                var extracted = _this4.extractFromKey(k, options);
                var key = extracted.key;
                usedKey = key;
                var namespaces = extracted.namespaces;
                if (_this4.options.fallbackNS) namespaces = namespaces.concat(_this4.options.fallbackNS);

                var needsPluralHandling = options.count !== undefined && typeof options.count !== 'string';
                var needsContextHandling = options.context !== undefined && typeof options.context === 'string' && options.context !== '';

                var codes = options.lngs ? options.lngs : _this4.languageUtils.toResolveHierarchy(options.lng || _this4.language);

                namespaces.forEach(function (ns) {
                    if (_this4.isValidLookup(found)) return;
                    usedNS = ns;

                    codes.forEach(function (code) {
                        if (_this4.isValidLookup(found)) return;
                        usedLng = code;

                        var finalKey = key;
                        var finalKeys = [finalKey];

                        if (_this4.i18nFormat && _this4.i18nFormat.addLookupKeys) {
                            _this4.i18nFormat.addLookupKeys(finalKeys, key, code, ns, options);
                        } else {
                            var pluralSuffix = void 0;
                            if (needsPluralHandling) pluralSuffix = _this4.pluralResolver.getSuffix(code, options.count);

                            // fallback for plural if context not found
                            if (needsPluralHandling && needsContextHandling) finalKeys.push(finalKey + pluralSuffix);

                            // get key for context if needed
                            if (needsContextHandling) finalKeys.push(finalKey += '' + _this4.options.contextSeparator + options.context);

                            // get key for plural if needed
                            if (needsPluralHandling) finalKeys.push(finalKey += pluralSuffix);
                        }

                        // iterate over finalKeys starting with most specific pluralkey (-> contextkey only) -> singularkey only
                        var possibleKey = void 0;
                        /* eslint no-cond-assign: 0 */
                        while (possibleKey = finalKeys.pop()) {
                            if (!_this4.isValidLookup(found)) {
                                found = _this4.getResource(code, ns, possibleKey, options);
                            }
                        }
                    });
                });
            });

            return {
                res: found,
                usedKey: usedKey,
                usedLng: usedLng,
                usedNS: usedNS
            };
        };

        Translator.prototype.isValidLookup = function isValidLookup(res) {
            return res !== undefined && !(!this.options.returnNull && res === null) && !(!this.options.returnEmptyString && res === '');
        };

        Translator.prototype.getResource = function getResource(code, ns, key) {
            var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};

            return this.resourceStore.getResource(code, ns, key, options);
        };

        return Translator;
    }(EventEmitter);

    function capitalize(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }

    var LanguageUtil = function () {
        function LanguageUtil(options) {
            classCallCheck(this, LanguageUtil);

            this.options = options;

            this.whitelist = this.options.whitelist || false;
            this.logger = baseLogger.create('languageUtils');
        }

        LanguageUtil.prototype.getScriptPartFromCode = function getScriptPartFromCode(code) {
            if (!code || code.indexOf('-') < 0) return null;

            var p = code.split('-');
            if (p.length === 2) return null;
            p.pop();
            return this.formatLanguageCode(p.join('-'));
        };

        LanguageUtil.prototype.getLanguagePartFromCode = function getLanguagePartFromCode(code) {
            if (!code || code.indexOf('-') < 0) return code;

            var p = code.split('-');
            return this.formatLanguageCode(p[0]);
        };

        LanguageUtil.prototype.formatLanguageCode = function formatLanguageCode(code) {
            // http://www.iana.org/assignments/language-tags/language-tags.xhtml
            if (typeof code === 'string' && code.indexOf('-') > -1) {
                var specialCases = ['hans', 'hant', 'latn', 'cyrl', 'cans', 'mong', 'arab'];
                var p = code.split('-');

                if (this.options.lowerCaseLng) {
                    p = p.map(function (part) {
                        return part.toLowerCase();
                    });
                } else if (p.length === 2) {
                    p[0] = p[0].toLowerCase();
                    p[1] = p[1].toUpperCase();

                    if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
                } else if (p.length === 3) {
                    p[0] = p[0].toLowerCase();

                    // if lenght 2 guess it's a country
                    if (p[1].length === 2) p[1] = p[1].toUpperCase();
                    if (p[0] !== 'sgn' && p[2].length === 2) p[2] = p[2].toUpperCase();

                    if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
                    if (specialCases.indexOf(p[2].toLowerCase()) > -1) p[2] = capitalize(p[2].toLowerCase());
                }

                return p.join('-');
            }

            return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code;
        };

        LanguageUtil.prototype.isWhitelisted = function isWhitelisted(code) {
            if (this.options.load === 'languageOnly' || this.options.nonExplicitWhitelist) {
                code = this.getLanguagePartFromCode(code);
            }
            return !this.whitelist || !this.whitelist.length || this.whitelist.indexOf(code) > -1;
        };

        LanguageUtil.prototype.getFallbackCodes = function getFallbackCodes(fallbacks, code) {
            if (!fallbacks) return [];
            if (typeof fallbacks === 'string') fallbacks = [fallbacks];
            if (Object.prototype.toString.apply(fallbacks) === '[object Array]') return fallbacks;

            if (!code) return fallbacks.default || [];

            // asume we have an object defining fallbacks
            var found = fallbacks[code];
            if (!found) found = fallbacks[this.getScriptPartFromCode(code)];
            if (!found) found = fallbacks[this.formatLanguageCode(code)];
            if (!found) found = fallbacks.default;

            return found || [];
        };

        LanguageUtil.prototype.toResolveHierarchy = function toResolveHierarchy(code, fallbackCode) {
            var _this = this;

            var fallbackCodes = this.getFallbackCodes(fallbackCode || this.options.fallbackLng || [], code);

            var codes = [];
            var addCode = function addCode(c) {
                if (!c) return;
                if (_this.isWhitelisted(c)) {
                    codes.push(c);
                } else {
                    _this.logger.warn('rejecting non-whitelisted language code: ' + c);
                }
            };

            if (typeof code === 'string' && code.indexOf('-') > -1) {
                if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
                if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly') addCode(this.getScriptPartFromCode(code));
                if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
            } else if (typeof code === 'string') {
                addCode(this.formatLanguageCode(code));
            }

            fallbackCodes.forEach(function (fc) {
                if (codes.indexOf(fc) < 0) addCode(_this.formatLanguageCode(fc));
            });

            return codes;
        };

        return LanguageUtil;
    }();

    // definition http://translate.sourceforge.net/wiki/l10n/pluralforms
    /* eslint-disable */
    var sets = [{
        lngs: ['ach', 'ak', 'am', 'arn', 'br', 'fil', 'gun', 'ln', 'mfe', 'mg', 'mi', 'oc', 'pt', 'pt-BR', 'tg', 'ti', 'tr', 'uz', 'wa'],
        nr: [1, 2],
        fc: 1
    }, {
        lngs: ['af', 'an', 'ast', 'az', 'bg', 'bn', 'ca', 'da', 'de', 'dev', 'el', 'en', 'eo', 'es', 'et', 'eu', 'fi', 'fo', 'fur', 'fy', 'gl', 'gu', 'ha', 'he', 'hi', 'hu', 'hy', 'ia', 'it', 'kn', 'ku', 'lb', 'mai', 'ml', 'mn', 'mr', 'nah', 'nap', 'nb', 'ne', 'nl', 'nn', 'no', 'nso', 'pa', 'pap', 'pms', 'ps', 'pt-PT', 'rm', 'sco', 'se', 'si', 'so', 'son', 'sq', 'sv', 'sw', 'ta', 'te', 'tk', 'ur', 'yo'],
        nr: [1, 2],
        fc: 2
    }, {
        lngs: ['ay', 'bo', 'cgg', 'fa', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky', 'lo', 'ms', 'sah', 'su', 'th', 'tt', 'ug', 'vi', 'wo', 'zh'],
        nr: [1],
        fc: 3
    }, {
        lngs: ['be', 'bs', 'dz', 'hr', 'ru', 'sr', 'uk'],
        nr: [1, 2, 5],
        fc: 4
    }, {
        lngs: ['ar'],
        nr: [0, 1, 2, 3, 11, 100],
        fc: 5
    }, {
        lngs: ['cs', 'sk'],
        nr: [1, 2, 5],
        fc: 6
    }, {
        lngs: ['csb', 'pl'],
        nr: [1, 2, 5],
        fc: 7
    }, {
        lngs: ['cy'],
        nr: [1, 2, 3, 8],
        fc: 8
    }, {
        lngs: ['fr'],
        nr: [1, 2],
        fc: 9
    }, {
        lngs: ['ga'],
        nr: [1, 2, 3, 7, 11],
        fc: 10
    }, {
        lngs: ['gd'],
        nr: [1, 2, 3, 20],
        fc: 11
    }, {
        lngs: ['is'],
        nr: [1, 2],
        fc: 12
    }, {
        lngs: ['jv'],
        nr: [0, 1],
        fc: 13
    }, {
        lngs: ['kw'],
        nr: [1, 2, 3, 4],
        fc: 14
    }, {
        lngs: ['lt'],
        nr: [1, 2, 10],
        fc: 15
    }, {
        lngs: ['lv'],
        nr: [1, 2, 0],
        fc: 16
    }, {
        lngs: ['mk'],
        nr: [1, 2],
        fc: 17
    }, {
        lngs: ['mnk'],
        nr: [0, 1, 2],
        fc: 18
    }, {
        lngs: ['mt'],
        nr: [1, 2, 11, 20],
        fc: 19
    }, {
        lngs: ['or'],
        nr: [2, 1],
        fc: 2
    }, {
        lngs: ['ro'],
        nr: [1, 2, 20],
        fc: 20
    }, {
        lngs: ['sl'],
        nr: [5, 1, 2, 3],
        fc: 21
    }];

    var _rulesPluralsTypes = {
        1: function _(n) {
            return Number(n > 1);
        },
        2: function _(n) {
            return Number(n != 1);
        },
        3: function _(n) {
            return 0;
        },
        4: function _(n) {
            return Number(n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
        },
        5: function _(n) {
            return Number(n === 0 ? 0 : n == 1 ? 1 : n == 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5);
        },
        6: function _(n) {
            return Number(n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2);
        },
        7: function _(n) {
            return Number(n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
        },
        8: function _(n) {
            return Number(n == 1 ? 0 : n == 2 ? 1 : n != 8 && n != 11 ? 2 : 3);
        },
        9: function _(n) {
            return Number(n >= 2);
        },
        10: function _(n) {
            return Number(n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4);
        },
        11: function _(n) {
            return Number(n == 1 || n == 11 ? 0 : n == 2 || n == 12 ? 1 : n > 2 && n < 20 ? 2 : 3);
        },
        12: function _(n) {
            return Number(n % 10 != 1 || n % 100 == 11);
        },
        13: function _(n) {
            return Number(n !== 0);
        },
        14: function _(n) {
            return Number(n == 1 ? 0 : n == 2 ? 1 : n == 3 ? 2 : 3);
        },
        15: function _(n) {
            return Number(n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
        },
        16: function _(n) {
            return Number(n % 10 == 1 && n % 100 != 11 ? 0 : n !== 0 ? 1 : 2);
        },
        17: function _(n) {
            return Number(n == 1 || n % 10 == 1 ? 0 : 1);
        },
        18: function _(n) {
            return Number(n == 0 ? 0 : n == 1 ? 1 : 2);
        },
        19: function _(n) {
            return Number(n == 1 ? 0 : n === 0 || n % 100 > 1 && n % 100 < 11 ? 1 : n % 100 > 10 && n % 100 < 20 ? 2 : 3);
        },
        20: function _(n) {
            return Number(n == 1 ? 0 : n === 0 || n % 100 > 0 && n % 100 < 20 ? 1 : 2);
        },
        21: function _(n) {
            return Number(n % 100 == 1 ? 1 : n % 100 == 2 ? 2 : n % 100 == 3 || n % 100 == 4 ? 3 : 0);
        }
    };
    /* eslint-enable */

    function createRules() {
        var rules = {};
        sets.forEach(function (set$$1) {
            set$$1.lngs.forEach(function (l) {
                rules[l] = {
                    numbers: set$$1.nr,
                    plurals: _rulesPluralsTypes[set$$1.fc]
                };
            });
        });
        return rules;
    }

    var PluralResolver = function () {
        function PluralResolver(languageUtils) {
            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
            classCallCheck(this, PluralResolver);

            this.languageUtils = languageUtils;
            this.options = options;

            this.logger = baseLogger.create('pluralResolver');

            this.rules = createRules();
        }

        PluralResolver.prototype.addRule = function addRule(lng, obj) {
            this.rules[lng] = obj;
        };

        PluralResolver.prototype.getRule = function getRule(code) {
            return this.rules[code] || this.rules[this.languageUtils.getLanguagePartFromCode(code)];
        };

        PluralResolver.prototype.needsPlural = function needsPlural(code) {
            var rule = this.getRule(code);

            return rule && rule.numbers.length > 1;
        };

        PluralResolver.prototype.getPluralFormsOfKey = function getPluralFormsOfKey(code, key) {
            var _this = this;

            var ret = [];

            var rule = this.getRule(code);

            if (!rule) return ret;

            rule.numbers.forEach(function (n) {
                var suffix = _this.getSuffix(code, n);
                ret.push('' + key + suffix);
            });

            return ret;
        };

        PluralResolver.prototype.getSuffix = function getSuffix(code, count) {
            var _this2 = this;

            var rule = this.getRule(code);

            if (rule) {
                // if (rule.numbers.length === 1) return ''; // only singular

                var idx = rule.noAbs ? rule.plurals(count) : rule.plurals(Math.abs(count));
                var suffix = rule.numbers[idx];

                // special treatment for lngs only having singular and plural
                if (this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
                    if (suffix === 2) {
                        suffix = 'plural';
                    } else if (suffix === 1) {
                        suffix = '';
                    }
                }

                var returnSuffix = function returnSuffix() {
                    return _this2.options.prepend && suffix.toString() ? _this2.options.prepend + suffix.toString() : suffix.toString();
                };

                // COMPATIBILITY JSON
                // v1
                if (this.options.compatibilityJSON === 'v1') {
                    if (suffix === 1) return '';
                    if (typeof suffix === 'number') return '_plural_' + suffix.toString();
                    return returnSuffix();
                } else if ( /* v2 */ this.options.compatibilityJSON === 'v2' || rule.numbers.length === 2 && rule.numbers[0] === 1) {
                    return returnSuffix();
                } else if ( /* v3 - gettext index */ rule.numbers.length === 2 && rule.numbers[0] === 1) {
                    return returnSuffix();
                }
                return this.options.prepend && idx.toString() ? this.options.prepend + idx.toString() : idx.toString();
            }

            this.logger.warn('no plural rule found for: ' + code);
            return '';
        };

        return PluralResolver;
    }();

    var Interpolator = function () {
        function Interpolator() {
            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            classCallCheck(this, Interpolator);

            this.logger = baseLogger.create('interpolator');

            this.init(options, true);
        }

        /* eslint no-param-reassign: 0 */


        Interpolator.prototype.init = function init() {
            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            var reset = arguments[1];

            if (reset) {
                this.options = options;
                this.format = options.interpolation && options.interpolation.format || function (value) {
                    return value;
                };
                this.escape = options.interpolation && options.interpolation.escape || escape;
            }
            if (!options.interpolation) options.interpolation = {
                escapeValue: true
            };

            var iOpts = options.interpolation;

            this.escapeValue = iOpts.escapeValue !== undefined ? iOpts.escapeValue : true;

            this.prefix = iOpts.prefix ? regexEscape(iOpts.prefix) : iOpts.prefixEscaped || '{{';
            this.suffix = iOpts.suffix ? regexEscape(iOpts.suffix) : iOpts.suffixEscaped || '}}';

            this.formatSeparator = iOpts.formatSeparator ? iOpts.formatSeparator : iOpts.formatSeparator || ',';

            this.unescapePrefix = iOpts.unescapeSuffix ? '' : iOpts.unescapePrefix || '-';
            this.unescapeSuffix = this.unescapePrefix ? '' : iOpts.unescapeSuffix || '';

            this.nestingPrefix = iOpts.nestingPrefix ? regexEscape(iOpts.nestingPrefix) : iOpts.nestingPrefixEscaped || regexEscape('$t(');
            this.nestingSuffix = iOpts.nestingSuffix ? regexEscape(iOpts.nestingSuffix) : iOpts.nestingSuffixEscaped || regexEscape(')');

            this.maxReplaces = iOpts.maxReplaces ? iOpts.maxReplaces : 1000;

            // the regexp
            this.resetRegExp();
        };

        Interpolator.prototype.reset = function reset() {
            if (this.options) this.init(this.options);
        };

        Interpolator.prototype.resetRegExp = function resetRegExp() {
            // the regexp
            var regexpStr = this.prefix + '(.+?)' + this.suffix;
            this.regexp = new RegExp(regexpStr, 'g');

            var regexpUnescapeStr = '' + this.prefix + this.unescapePrefix + '(.+?)' + this.unescapeSuffix + this.suffix;
            this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');

            var nestingRegexpStr = this.nestingPrefix + '(.+?)' + this.nestingSuffix;
            this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
        };

        Interpolator.prototype.interpolate = function interpolate(str, data, lng) {
            var _this = this;

            var match = void 0;
            var value = void 0;
            var replaces = void 0;

            function regexSafe(val) {
                return val.replace(/\$/g, '$$$$');
            }

            var handleFormat = function handleFormat(key) {
                if (key.indexOf(_this.formatSeparator) < 0) return getPath(data, key);

                var p = key.split(_this.formatSeparator);
                var k = p.shift().trim();
                var f = p.join(_this.formatSeparator).trim();

                return _this.format(getPath(data, k), f, lng);
            };

            this.resetRegExp();

            replaces = 0;
            // unescape if has unescapePrefix/Suffix
            /* eslint no-cond-assign: 0 */
            while (match = this.regexpUnescape.exec(str)) {
                value = handleFormat(match[1].trim());
                str = str.replace(match[0], value);
                this.regexpUnescape.lastIndex = 0;
                replaces++;
                if (replaces >= this.maxReplaces) {
                    break;
                }
            }

            replaces = 0;
            // regular escape on demand
            while (match = this.regexp.exec(str)) {
                value = handleFormat(match[1].trim());
                if (value === undefined) {
                    if (typeof this.options.missingInterpolationHandler === 'function') {
                        var temp = this.options.missingInterpolationHandler(str, match);
                        value = typeof temp === 'string' ? temp : '';
                    } else {
                        this.logger.warn('missed to pass in variable ' + match[1] + ' for interpolating ' + str);
                        value = '';
                    }
                } else if (typeof value !== 'string') {
                    value = makeString(value);
                }
                value = this.escapeValue ? regexSafe(this.escape(value)) : regexSafe(value);
                str = str.replace(match[0], value);
                this.regexp.lastIndex = 0;
                replaces++;
                if (replaces >= this.maxReplaces) {
                    break;
                }
            }
            return str;
        };

        Interpolator.prototype.nest = function nest(str, fc) {
            var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

            var match = void 0;
            var value = void 0;

            var clonedOptions = _extends({}, options);
            clonedOptions.applyPostProcessor = false; // avoid post processing on nested lookup

            // if value is something like "myKey": "lorem $(anotherKey, { "count": {{aValueInOptions}} })"
            function handleHasOptions(key, inheritedOptions) {
                if (key.indexOf(',') < 0) return key;

                var p = key.split(',');
                key = p.shift();
                var optionsString = p.join(',');
                optionsString = this.interpolate(optionsString, clonedOptions);
                optionsString = optionsString.replace(/'/g, '"');

                try {
                    clonedOptions = JSON.parse(optionsString);

                    if (inheritedOptions) clonedOptions = _extends({}, inheritedOptions, clonedOptions);
                } catch (e) {
                    this.logger.error('failed parsing options string in nesting for key ' + key, e);
                }

                return key;
            }

            // regular escape on demand
            while (match = this.nestingRegexp.exec(str)) {
                value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);

                // is only the nesting key (key1 = '$(key2)') return the value without stringify
                if (value && match[0] === str && typeof value !== 'string') return value;

                // no string to include or empty
                if (typeof value !== 'string') value = makeString(value);
                if (!value) {
                    this.logger.warn('missed to resolve ' + match[1] + ' for nesting ' + str);
                    value = '';
                }
                // Nested keys should not be escaped by default #854
                // value = this.escapeValue ? regexSafe(utils.escape(value)) : regexSafe(value);
                str = str.replace(match[0], value);
                this.regexp.lastIndex = 0;
            }
            return str;
        };

        return Interpolator;
    }();

    function remove(arr, what) {
        var found = arr.indexOf(what);

        while (found !== -1) {
            arr.splice(found, 1);
            found = arr.indexOf(what);
        }
    }

    var Connector = function (_EventEmitter) {
        inherits(Connector, _EventEmitter);

        function Connector(backend, store, services) {
            var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
            classCallCheck(this, Connector);

            var _this = possibleConstructorReturn(this, _EventEmitter.call(this));

            _this.backend = backend;
            _this.store = store;
            _this.languageUtils = services.languageUtils;
            _this.options = options;
            _this.logger = baseLogger.create('backendConnector');

            _this.state = {};
            _this.queue = [];

            if (_this.backend && _this.backend.init) {
                _this.backend.init(services, options.backend, options);
            }
            return _this;
        }

        Connector.prototype.queueLoad = function queueLoad(languages, namespaces, callback) {
            var _this2 = this;

            // find what needs to be loaded
            var toLoad = [];
            var pending = [];
            var toLoadLanguages = [];
            var toLoadNamespaces = [];

            languages.forEach(function (lng) {
                var hasAllNamespaces = true;

                namespaces.forEach(function (ns) {
                    var name = lng + '|' + ns;

                    if (_this2.store.hasResourceBundle(lng, ns)) {
                        _this2.state[name] = 2; // loaded
                    } else if (_this2.state[name] < 0) {
                        // nothing to do for err
                    } else if (_this2.state[name] === 1) {
                        if (pending.indexOf(name) < 0) pending.push(name);
                    } else {
                        _this2.state[name] = 1; // pending

                        hasAllNamespaces = false;

                        if (pending.indexOf(name) < 0) pending.push(name);
                        if (toLoad.indexOf(name) < 0) toLoad.push(name);
                        if (toLoadNamespaces.indexOf(ns) < 0) toLoadNamespaces.push(ns);
                    }
                });

                if (!hasAllNamespaces) toLoadLanguages.push(lng);
            });

            if (toLoad.length || pending.length) {
                this.queue.push({
                    pending: pending,
                    loaded: {},
                    errors: [],
                    callback: callback
                });
            }

            return {
                toLoad: toLoad,
                pending: pending,
                toLoadLanguages: toLoadLanguages,
                toLoadNamespaces: toLoadNamespaces
            };
        };

        Connector.prototype.loaded = function loaded(name, err, data) {
            var _this3 = this;

            var _name$split = name.split('|'),
                _name$split2 = slicedToArray(_name$split, 2),
                lng = _name$split2[0],
                ns = _name$split2[1];

            if (err) this.emit('failedLoading', lng, ns, err);

            if (data) {
                this.store.addResourceBundle(lng, ns, data);
            }

            // set loaded
            this.state[name] = err ? -1 : 2;

            // callback if ready
            this.queue.forEach(function (q) {
                pushPath(q.loaded, [lng], ns);
                remove(q.pending, name);

                if (err) q.errors.push(err);

                if (q.pending.length === 0 && !q.done) {
                    _this3.emit('loaded', q.loaded);
                    /* eslint no-param-reassign: 0 */
                    q.done = true;
                    if (q.errors.length) {
                        q.callback(q.errors);
                    } else {
                        q.callback();
                    }
                }
            });

            // remove done load requests
            this.queue = this.queue.filter(function (q) {
                return !q.done;
            });
        };

        Connector.prototype.read = function read(lng, ns, fcName) {
            var tried = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;

            var _this4 = this;

            var wait = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 250;
            var callback = arguments[5];

            if (!lng.length) return callback(null, {}); // noting to load

            return this.backend[fcName](lng, ns, function (err, data) {
                if (err && data /* = retryFlag */ && tried < 5) {
                    setTimeout(function () {
                        _this4.read.call(_this4, lng, ns, fcName, tried + 1, wait * 2, callback);
                    }, wait);
                    return;
                }
                callback(err, data);
            });
        };

        /* eslint consistent-return: 0 */


        Connector.prototype.load = function load(languages, namespaces, callback) {
            var _this5 = this;

            if (!this.backend) {
                this.logger.warn('No backend was added via i18next.use. Will not load resources.');
                return callback && callback();
            }

            if (typeof languages === 'string') languages = this.languageUtils.toResolveHierarchy(languages);
            if (typeof namespaces === 'string') namespaces = [namespaces];

            var toLoad = this.queueLoad(languages, namespaces, callback);
            if (!toLoad.toLoad.length) {
                if (!toLoad.pending.length) callback(); // nothing to load and no pendings...callback now
                return null; // pendings will trigger callback
            }

            toLoad.toLoad.forEach(function (name) {
                _this5.loadOne(name);
            });
        };

        Connector.prototype.reload = function reload(languages, namespaces) {
            var _this6 = this;

            if (!this.backend) {
                this.logger.warn('No backend was added via i18next.use. Will not load resources.');
            }

            if (typeof languages === 'string') languages = this.languageUtils.toResolveHierarchy(languages);
            if (typeof namespaces === 'string') namespaces = [namespaces];

            languages.forEach(function (l) {
                namespaces.forEach(function (n) {
                    _this6.loadOne(l + '|' + n, 're');
                });
            });
        };

        Connector.prototype.loadOne = function loadOne(name) {
            var _this7 = this;

            var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

            var _name$split3 = name.split('|'),
                _name$split4 = slicedToArray(_name$split3, 2),
                lng = _name$split4[0],
                ns = _name$split4[1];

            this.read(lng, ns, 'read', null, null, function (err, data) {
                if (err) _this7.logger.warn(prefix + 'loading namespace ' + ns + ' for language ' + lng + ' failed', err);
                if (!err && data) _this7.logger.log(prefix + 'loaded namespace ' + ns + ' for language ' + lng, data);

                _this7.loaded(name, err, data);
            });
        };

        Connector.prototype.saveMissing = function saveMissing(languages, namespace, key, fallbackValue, isUpdate) {
            var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};

            if (this.backend && this.backend.create) {
                this.backend.create(languages, namespace, key, fallbackValue, null /* unused callback */ , _extends({}, options, {
                    isUpdate: isUpdate
                }));
            }

            // write to store to avoid resending
            if (!languages || !languages[0]) return;
            this.store.addResource(languages[0], namespace, key, fallbackValue);
        };

        return Connector;
    }(EventEmitter);

    function get$1() {
        return {
            debug: false,
            initImmediate: true,

            ns: ['translation'],
            defaultNS: ['translation'],
            fallbackLng: ['dev'],
            fallbackNS: false, // string or array of namespaces

            whitelist: false, // array with whitelisted languages
            nonExplicitWhitelist: false,
            load: 'all', // | currentOnly | languageOnly
            preload: false, // array with preload languages

            simplifyPluralSuffix: true,
            keySeparator: '.',
            nsSeparator: ':',
            pluralSeparator: '_',
            contextSeparator: '_',

            saveMissing: false, // enable to send missing values
            updateMissing: false, // enable to update default values if different from translated value (only useful on initial development, or when keeping code as source of truth)
            saveMissingTo: 'fallback', // 'current' || 'all'
            saveMissingPlurals: true, // will save all forms not only singular key
            missingKeyHandler: false, // function(lng, ns, key, fallbackValue) -> override if prefer on handling
            missingInterpolationHandler: false, // function(str, match)

            postProcess: false, // string or array of postProcessor names
            returnNull: true, // allows null value as valid translation
            returnEmptyString: true, // allows empty string value as valid translation
            returnObjects: false,
            joinArrays: false, // or string to join array
            returnedObjectHandler: function returnedObjectHandler() {}, // function(key, value, options) triggered if key returns object but returnObjects is set to false
            parseMissingKeyHandler: false, // function(key) parsed a key that was not found in t() before returning
            appendNamespaceToMissingKey: false,
            appendNamespaceToCIMode: false,
            overloadTranslationOptionHandler: function handle(args) {
                var ret = {};
                if (args[1]) ret.defaultValue = args[1];
                if (args[2]) ret.tDescription = args[2];
                return ret;
            },
            interpolation: {
                escapeValue: true,
                format: function format(value, _format, lng) {
                    return value;
                },
                prefix: '{{',
                suffix: '}}',
                formatSeparator: ',',
                // prefixEscaped: '{{',
                // suffixEscaped: '}}',
                // unescapeSuffix: '',
                unescapePrefix: '-',

                nestingPrefix: '$t(',
                nestingSuffix: ')',
                // nestingPrefixEscaped: '$t(',
                // nestingSuffixEscaped: ')',
                // defaultVariables: undefined // object that can have values to interpolate on - extends passed in interpolation data
                maxReplaces: 1000 // max replaces to prevent endless loop
            }
        };
    }

    /* eslint no-param-reassign: 0 */
    function transformOptions(options) {
        // create namespace object if namespace is passed in as string
        if (typeof options.ns === 'string') options.ns = [options.ns];
        if (typeof options.fallbackLng === 'string') options.fallbackLng = [options.fallbackLng];
        if (typeof options.fallbackNS === 'string') options.fallbackNS = [options.fallbackNS];

        // extend whitelist with cimode
        if (options.whitelist && options.whitelist.indexOf('cimode') < 0) {
            options.whitelist = options.whitelist.concat(['cimode']);
        }

        return options;
    }

    function noop() {}

    var I18n = function (_EventEmitter) {
        inherits(I18n, _EventEmitter);

        function I18n() {
            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            var callback = arguments[1];
            classCallCheck(this, I18n);

            var _this = possibleConstructorReturn(this, _EventEmitter.call(this));

            _this.options = transformOptions(options);
            _this.services = {};
            _this.logger = baseLogger;
            _this.modules = {
                external: []
            };

            if (callback && !_this.isInitialized && !options.isClone) {
                var _ret;

                // https://github.com/i18next/i18next/issues/879
                if (!_this.options.initImmediate) return _ret = _this.init(options, callback), possibleConstructorReturn(_this, _ret);
                setTimeout(function () {
                    _this.init(options, callback);
                }, 0);
            }
            return _this;
        }

        I18n.prototype.init = function init() {
            var _this2 = this;

            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            var callback = arguments[1];

            if (typeof options === 'function') {
                callback = options;
                options = {};
            }
            this.options = _extends({}, get$1(), this.options, transformOptions(options));

            this.format = this.options.interpolation.format;
            if (!callback) callback = noop;

            function createClassOnDemand(ClassOrObject) {
                if (!ClassOrObject) return null;
                if (typeof ClassOrObject === 'function') return new ClassOrObject();
                return ClassOrObject;
            }

            // init services
            if (!this.options.isClone) {
                if (this.modules.logger) {
                    baseLogger.init(createClassOnDemand(this.modules.logger), this.options);
                } else {
                    baseLogger.init(null, this.options);
                }

                var lu = new LanguageUtil(this.options);
                this.store = new ResourceStore(this.options.resources, this.options);

                var s = this.services;
                s.logger = baseLogger;
                s.resourceStore = this.store;
                s.languageUtils = lu;
                s.pluralResolver = new PluralResolver(lu, {
                    prepend: this.options.pluralSeparator,
                    compatibilityJSON: this.options.compatibilityJSON,
                    simplifyPluralSuffix: this.options.simplifyPluralSuffix
                });
                s.interpolator = new Interpolator(this.options);

                s.backendConnector = new Connector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options);
                // pipe events from backendConnector
                s.backendConnector.on('*', function (event) {
                    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
                        args[_key - 1] = arguments[_key];
                    }

                    _this2.emit.apply(_this2, [event].concat(args));
                });

                if (this.modules.languageDetector) {
                    s.languageDetector = createClassOnDemand(this.modules.languageDetector);
                    s.languageDetector.init(s, this.options.detection, this.options);
                }

                if (this.modules.i18nFormat) {
                    s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
                    if (s.i18nFormat.init) s.i18nFormat.init(this);
                }

                this.translator = new Translator(this.services, this.options);
                // pipe events from translator
                this.translator.on('*', function (event) {
                    for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
                        args[_key2 - 1] = arguments[_key2];
                    }

                    _this2.emit.apply(_this2, [event].concat(args));
                });

                this.modules.external.forEach(function (m) {
                    if (m.init) m.init(_this2);
                });
            }

            // append api
            var storeApi = ['getResource', 'addResource', 'addResources', 'addResourceBundle', 'removeResourceBundle', 'hasResourceBundle', 'getResourceBundle'];
            storeApi.forEach(function (fcName) {
                _this2[fcName] = function () {
                    var _store;

                    return (_store = _this2.store)[fcName].apply(_store, arguments);
                };
            });

            var load = function load() {
                _this2.changeLanguage(_this2.options.lng, function (err, t) {
                    _this2.isInitialized = true;
                    _this2.logger.log('initialized', _this2.options);
                    _this2.emit('initialized', _this2.options);

                    callback(err, t);
                });
            };

            if (this.options.resources || !this.options.initImmediate) {
                load();
            } else {
                setTimeout(load, 0);
            }

            return this;
        };

        /* eslint consistent-return: 0 */


        I18n.prototype.loadResources = function loadResources() {
            var _this3 = this;

            var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop;

            if (!this.options.resources) {
                if (this.language && this.language.toLowerCase() === 'cimode') return callback(); // avoid loading resources for cimode

                var toLoad = [];

                var append = function append(lng) {
                    if (!lng) return;
                    var lngs = _this3.services.languageUtils.toResolveHierarchy(lng);
                    lngs.forEach(function (l) {
                        if (toLoad.indexOf(l) < 0) toLoad.push(l);
                    });
                };

                if (!this.language) {
                    // at least load fallbacks in this case
                    var fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
                    fallbacks.forEach(function (l) {
                        return append(l);
                    });
                } else {
                    append(this.language);
                }

                if (this.options.preload) {
                    this.options.preload.forEach(function (l) {
                        return append(l);
                    });
                }

                this.services.backendConnector.load(toLoad, this.options.ns, callback);
            } else {
                callback(null);
            }
        };

        I18n.prototype.reloadResources = function reloadResources(lngs, ns) {
            if (!lngs) lngs = this.languages;
            if (!ns) ns = this.options.ns;
            this.services.backendConnector.reload(lngs, ns);
        };

        I18n.prototype.use = function use(module) {
            if (module.type === 'backend') {
                this.modules.backend = module;
            }

            if (module.type === 'logger' || module.log && module.warn && module.error) {
                this.modules.logger = module;
            }

            if (module.type === 'languageDetector') {
                this.modules.languageDetector = module;
            }

            if (module.type === 'i18nFormat') {
                this.modules.i18nFormat = module;
            }

            if (module.type === 'postProcessor') {
                postProcessor.addPostProcessor(module);
            }

            if (module.type === '3rdParty') {
                this.modules.external.push(module);
            }

            return this;
        };

        I18n.prototype.changeLanguage = function changeLanguage(lng, callback) {
            var _this4 = this;

            var done = function done(err, l) {
                _this4.translator.changeLanguage(l);

                if (l) {
                    _this4.emit('languageChanged', l);
                    _this4.logger.log('languageChanged', l);
                }

                if (callback) callback(err, function () {
                    return _this4.t.apply(_this4, arguments);
                });
            };

            var setLng = function setLng(l) {
                if (l) {
                    _this4.language = l;
                    _this4.languages = _this4.services.languageUtils.toResolveHierarchy(l);
                    if (!_this4.translator.language) _this4.translator.changeLanguage(l);

                    if (_this4.services.languageDetector) _this4.services.languageDetector.cacheUserLanguage(l);
                }

                _this4.loadResources(function (err) {
                    done(err, l);
                });
            };

            if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
                setLng(this.services.languageDetector.detect());
            } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
                this.services.languageDetector.detect(setLng);
            } else {
                setLng(lng);
            }
        };

        I18n.prototype.getFixedT = function getFixedT(lng, ns) {
            var _this5 = this;

            var fixedT = function fixedT(key, opts) {
                for (var _len3 = arguments.length, rest = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
                    rest[_key3 - 2] = arguments[_key3];
                }

                var options = _extends({}, opts);
                if ((typeof opts === 'undefined' ? 'undefined' : _typeof(opts)) !== 'object') {
                    options = _this5.options.overloadTranslationOptionHandler([key, opts].concat(rest));
                }

                options.lng = options.lng || fixedT.lng;
                options.lngs = options.lngs || fixedT.lngs;
                options.ns = options.ns || fixedT.ns;
                return _this5.t(key, options);
            };
            if (typeof lng === 'string') {
                fixedT.lng = lng;
            } else {
                fixedT.lngs = lng;
            }
            fixedT.ns = ns;
            return fixedT;
        };

        I18n.prototype.t = function t() {
            var _translator;

            return this.translator && (_translator = this.translator).translate.apply(_translator, arguments);
        };

        I18n.prototype.exists = function exists() {
            var _translator2;

            return this.translator && (_translator2 = this.translator).exists.apply(_translator2, arguments);
        };

        I18n.prototype.setDefaultNamespace = function setDefaultNamespace(ns) {
            this.options.defaultNS = ns;
        };

        I18n.prototype.loadNamespaces = function loadNamespaces(ns, callback) {
            var _this6 = this;

            if (!this.options.ns) return callback && callback();
            if (typeof ns === 'string') ns = [ns];

            ns.forEach(function (n) {
                if (_this6.options.ns.indexOf(n) < 0) _this6.options.ns.push(n);
            });

            this.loadResources(callback);
        };

        I18n.prototype.loadLanguages = function loadLanguages(lngs, callback) {
            if (typeof lngs === 'string') lngs = [lngs];
            var preloaded = this.options.preload || [];

            var newLngs = lngs.filter(function (lng) {
                return preloaded.indexOf(lng) < 0;
            });
            // Exit early if all given languages are already preloaded
            if (!newLngs.length) return callback();

            this.options.preload = preloaded.concat(newLngs);
            this.loadResources(callback);
        };

        I18n.prototype.dir = function dir(lng) {
            if (!lng) lng = this.languages && this.languages.length > 0 ? this.languages[0] : this.language;
            if (!lng) return 'rtl';

            var rtlLngs = ['ar', 'shu', 'sqr', 'ssh', 'xaa', 'yhd', 'yud', 'aao', 'abh', 'abv', 'acm', 'acq', 'acw', 'acx', 'acy', 'adf', 'ads', 'aeb', 'aec', 'afb', 'ajp', 'apc', 'apd', 'arb', 'arq', 'ars', 'ary', 'arz', 'auz', 'avl', 'ayh', 'ayl', 'ayn', 'ayp', 'bbz', 'pga', 'he', 'iw', 'ps', 'pbt', 'pbu', 'pst', 'prp', 'prd', 'ur', 'ydd', 'yds', 'yih', 'ji', 'yi', 'hbo', 'men', 'xmn', 'fa', 'jpr', 'peo', 'pes', 'prs', 'dv', 'sam'];

            return rtlLngs.indexOf(this.services.languageUtils.getLanguagePartFromCode(lng)) >= 0 ? 'rtl' : 'ltr';
        };

        /* eslint class-methods-use-this: 0 */


        I18n.prototype.createInstance = function createInstance() {
            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            var callback = arguments[1];

            return new I18n(options, callback);
        };

        I18n.prototype.cloneInstance = function cloneInstance() {
            var _this7 = this;

            var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
            var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;

            var mergedOptions = _extends({}, this.options, options, {
                isClone: true
            });
            var clone = new I18n(mergedOptions);
            var membersToCopy = ['store', 'services', 'language'];
            membersToCopy.forEach(function (m) {
                clone[m] = _this7[m];
            });
            clone.translator = new Translator(clone.services, clone.options);
            clone.translator.on('*', function (event) {
                for (var _len4 = arguments.length, args = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
                    args[_key4 - 1] = arguments[_key4];
                }

                clone.emit.apply(clone, [event].concat(args));
            });
            clone.init(mergedOptions, callback);
            clone.translator.options = clone.options; // sync options

            return clone;
        };

        return I18n;
    }(EventEmitter);

    var i18next = new I18n();

    return i18next;

})));